package com.yuzhi.framework.config;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.yuzhi.common.jackson.BigNumberSerializer;
import com.yuzhi.common.jackson.DictFormatSerializerModifier;
import com.yuzhi.common.jackson.EnumTranSerializerModifier;
import com.yuzhi.framework.handler.geometry.Coordinate;
import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.Point;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.TimeZone;

/**
 * jackson 配置
 *
 * @author sunshine
 */
@Slf4j
@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            // 全局配置序列化返回 JSON 处理
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
            javaTimeModule.addSerializer(Point.class, new PointSerializer());
            javaTimeModule.addSerializer(Geometry.class, new GeometrySerializer());
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
            javaTimeModule.addDeserializer(Point.class, new PointDeserializer());
            javaTimeModule.addDeserializer(Geometry.class, new GeometryDeserializer());

            SimpleModule simpleModule = new SimpleModule();
            simpleModule.setSerializerModifier(new EnumTranSerializerModifier());
            simpleModule.setSerializerModifier(new DictFormatSerializerModifier());

            builder.modules(javaTimeModule,simpleModule);
            builder.timeZone(TimeZone.getDefault());
            log.info("初始化 jackson 配置");
        };
    }

    /**
     * 序列化
     */
    public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException {
            if (value != null){
                long timestamp = value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                gen.writeNumber(timestamp);
            }
        }
    }

    /**
     * 反序列化
     */
    public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext)
                throws IOException {
            long timestamp = p.getValueAsLong();
            if (timestamp > 0){
                return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp),ZoneId.systemDefault());
            }else{
                return null;
            }
        }
    }

    /**
     * Point序列化
     */
    public static class PointSerializer extends JsonSerializer<Point> {
        @Override
        public void serialize(Point value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeObject(Coordinate.from(value));
        }
    }

    /**
     * Geometry序列化
     */
    public static class GeometrySerializer extends JsonSerializer<Geometry> {
        @Override
        public void serialize(Geometry value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value instanceof LineString) {
                gen.writeObject(Coordinate.from((LineString) value));
            } else if (value instanceof MultiLineString) {
                gen.writeObject(Coordinate.from((MultiLineString) value));
            } else if (value instanceof Point) {
                gen.writeObject(Coordinate.from((Point) value));
            }
        }
    }

    /**
     * Point反序列化
     */
    public static class PointDeserializer extends JsonDeserializer<Point> {
        @Override
        public Point deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
            var coordinate = p.readValueAs(Coordinate.class);
            return coordinate.toPoint();
        }
    }
    /**
     * Geometry反序列化
     */
    public static class GeometryDeserializer extends JsonDeserializer<Geometry> {
        @Override
        public Geometry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
            return Try.of(() -> p.readValueAs(new TypeReference<List<Coordinate>>() {}))
                    .orElse(Try.of(() -> p.readValueAs(Coordinate.class)))
                    .map(r -> {
                        if (r instanceof List) {
                            return Coordinate.toLineString((List<Coordinate>) r);
                        }else {
                            return  ((Coordinate)r).toPoint();
                        }
                    }).getOrNull();
        }
    }
}
